﻿// Decompiled with JetBrains decompiler
// Type: Netick.Simulation
// Assembly: Netick, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 6726ECA1-C773-4CF7-8952-E81B30D93B52
// Assembly location: D:\WorkSpaceDemo\UPM\NetickForUnity\Netick\Runtime\Netick.dll
// XML documentation location: D:\WorkSpaceDemo\UPM\NetickForUnity\Netick\Runtime\Netick.xml

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

#nullable disable
namespace Netick;

public abstract class Simulation
{
  internal NetickEngine Engine;
  internal LinkedAllocator StateAllocator;
  internal NetickAllocator MetaAllocator;
  internal List<NetickAllocator.MemoryPool> WorldPools;
  internal List<NetickAllocator.MemoryPool> PreviousPools;
  internal NetickAllocator Previous;
  internal NetickAllocator From;
  internal NetickAllocator To;
  internal ExecutionListLL SimulatedEntities;
  internal Dictionary<int, Entity> AllEntities;
  internal Dictionary<int, Entity> ActiveEntities;
  internal List<Entity> EntitiesList;
  internal Entity[] EntitiesWorldIndexArray;
  public Dictionary<int, Entity> UnlinkedEntities;
  protected Queue<Simulation.BehaviourAttachOperation> _pendingBehaviours;
  protected HashSet<Entity> _pendingAddedEntities;
  protected HashSet<Simulation.EntityRemove> _pendingRemovedEntities;
  protected ExecutionList _networkStartExecutionList;
  protected List<Entity> _addedEntities;
  protected List<Simulation.EntityRemove> _removedEntitiesList;
  private int _idCounter = 1;
  internal bool _inputEnabled = true;
  private FastAllocator _inputAllocator;
  private Dictionary<int, InputBlock> _inputProvidersUsed;
  private Dictionary<int, InputBlock> _inputProviderTable;
  private Queue<SimulationInput> _inputsPool = new Queue<SimulationInput>(512 /*0x0200*/);
  private Dictionary<int, InputBlock> _lastInputsCache = new Dictionary<int, InputBlock>(512 /*0x0200*/);
  private const float LocalAlphaPrecisionInverse = 10000f;
  private const float LocalAlphaPrecision = 0.0001f;
  internal HashSet<OnChangedData> _changesCache = new HashSet<OnChangedData>(2048 /*0x0800*/);

  public SimulationInput CurrentInput { get; internal set; }

  internal Simulation(NetickEngine engine)
  {
    this.Engine = engine;
    this.StateAllocator = new LinkedAllocator((long) this.Engine._config.StateAllocatorBlockSize);
    this.Previous = new NetickAllocator((long) this.Engine._config.StateAllocatorBlockSize);
    this.WorldPools = this.StateAllocator._allocator.Blocks;
    this.PreviousPools = this.Previous.Blocks;
    this.StateAllocator.Link(this.Previous);
    this.MetaAllocator = new NetickAllocator((long) this.Engine._config.MetaAllocatorBlockSize);
    this.SimulatedEntities = new ExecutionListLL(engine);
    this.AllEntities = new Dictionary<int, Entity>(this.Engine.Config.MaxObjects * 2);
    this.ActiveEntities = new Dictionary<int, Entity>(this.Engine.Config.MaxObjects * 2);
    this.EntitiesList = new List<Entity>(this.Engine.Config.MaxObjects);
    this.EntitiesWorldIndexArray = new Entity[this.Engine.Config.MaxObjects];
    this.UnlinkedEntities = new Dictionary<int, Entity>(this.Engine.Config.MaxObjects);
    this._pendingBehaviours = new Queue<Simulation.BehaviourAttachOperation>(128 /*0x80*/);
    this._pendingAddedEntities = new HashSet<Entity>(this.Engine.Config.MaxObjects);
    this._pendingRemovedEntities = new HashSet<Simulation.EntityRemove>(this.Engine.Config.MaxObjects);
    this._removedEntitiesList = new List<Simulation.EntityRemove>(this.Engine.Config.MaxObjects);
    this._addedEntities = new List<Entity>(this.Engine.Config.MaxObjects);
    this._networkStartExecutionList = new ExecutionList(this.Engine);
    this._inputAllocator = new FastAllocator(131072L /*0x020000*/);
    this._inputProvidersUsed = new Dictionary<int, InputBlock>(this.Engine._reflectionData.InputTypeToId.Count);
    this._inputProviderTable = new Dictionary<int, InputBlock>(this.Engine._reflectionData.InputTypeToId.Count);
    for (int index = 0; index < 512 /*0x0200*/; ++index)
      this._inputsPool.Enqueue(new SimulationInput());
  }

  internal virtual void Reset()
  {
  }

  internal virtual void InitEntity(Entity entity, NetickAllocator source, bool isUnliked)
  {
  }

  internal virtual void Step()
  {
  }

  internal void InvokeNetworkFixedUpdate()
  {
    if (!this.Engine._simulate || this.Engine.IsClient && !this.Engine.ClientSimulation.AuthoritativeTick.IsValid)
      return;
    this.Engine._profiler.FixedUpdateProfiler?.Begin();
    Action networkFixedUpdate1 = this.Engine.PreNetworkFixedUpdate;
    if (networkFixedUpdate1 != null)
      networkFixedUpdate1();
    this.Engine._sim.SimulatedEntities.ExecuteNetworkFixedUpdate();
    Action networkFixedUpdate2 = this.Engine.PostNetworkFixedUpdate;
    if (networkFixedUpdate2 != null)
      networkFixedUpdate2();
    this.Engine._profiler.FixedUpdateProfiler?.End();
  }

  internal virtual void PostStep()
  {
    if (this.Engine.IsClient)
      this.Engine._gameEngine.SyncTransformsToColliders();
    if (this.Engine.IsClient && this.Engine.IsConnected && this.Engine._cliSim.AuthoritativeTick.IsValid && this.Engine._cliSim.CurrentTick.IsValid)
    {
      ++this.Engine._cliSim.CurrentTick;
      this.Engine._cliSim.PredictedTick = this.Engine._cliSim.CurrentTick;
      this.InvokeGameEngineIntoNetcode();
    }
    if (!this.Engine._timer.IsLastCall)
      return;
    foreach (InputBlock inputBlock in this._inputProvidersUsed.Values)
      inputBlock.Reset();
    this._inputProvidersUsed.Clear();
  }

  internal void InvokeGameEngineIntoNetcode()
  {
    this.Engine._profiler.GameEngineIntoNetcodeProfiler?.Begin();
    if (this.Engine.IsServer)
      this.SimulatedEntities.ExecuteGameEngineIntoNetcode();
    else
      this.Engine._cliSim.PredictedEntities.ExecuteGameEngineIntoNetcode();
    this.Engine._profiler.GameEngineIntoNetcodeProfiler?.End();
  }

  public void RemoveFromNetworkLoop(Entity entity)
  {
    this.SimulatedEntities.RemoveFromSimulation(entity);
    this.EntitiesList.RemoveUnordered(entity);
  }

  internal void DrainPendingSpawnedObjects()
  {
    foreach (Entity pendingAddedEntity in this._pendingAddedEntities)
    {
      this.EntitiesList.Add(pendingAddedEntity);
      this._addedEntities.Add(pendingAddedEntity);
      if (pendingAddedEntity.UserEntity.AddToNetworkLoop)
        this.SimulatedEntities.AddToSimulation(pendingAddedEntity);
    }
    for (int index = 0; index < this._addedEntities.Count; ++index)
    {
      Entity addedEntity = this._addedEntities[index];
      this._pendingAddedEntities.Remove(addedEntity);
      this._networkStartExecutionList.Add(addedEntity);
      addedEntity.InternalInterestGroup?.Entity.InterestMarkAltered(addedEntity.GroupTableId);
      addedEntity.AoILayer?.MoveNoCheck(addedEntity, addedEntity.UserEntity.WorldPosition);
    }
    this._networkStartExecutionList.ExecuteNetworkStartAndClear();
    for (int index = 0; index < this._addedEntities.Count; ++index)
    {
      Entity addedEntity = this._addedEntities[index];
      if (addedEntity.InternalInputSourceIndex != -1)
        addedEntity.InvokeOnInputSourceChanged((NetworkPlayer) null);
      addedEntity.AoILayer?.MoveNoCheck(addedEntity, addedEntity.UserEntity.WorldPosition);
      addedEntity.IsReady = true;
    }
    this._addedEntities.Clear();
  }

  internal void DrainPendingDestroyedObjects()
  {
    foreach (Simulation.EntityRemove pendingRemovedEntity in this._pendingRemovedEntities)
      this._removedEntitiesList.Add(pendingRemovedEntity);
    foreach (Simulation.EntityRemove removedEntities in this._removedEntitiesList)
    {
      this._pendingRemovedEntities.Remove(removedEntities);
      this.RemoveEntity(removedEntities.Entity, removedEntities.FreeMem, false);
    }
    this._removedEntitiesList.Clear();
  }

  public void ClientAddUnlinkedEntity(Entity entity, int id, int worldIndex)
  {
    entity.RawId = id;
    entity.WorldIndex = worldIndex;
    this.UnlinkedEntities.Add(id, entity);
    this.Engine._cliSim.InitUnlinkedEntity(entity);
    this.EntitiesWorldIndexArray[worldIndex] = entity;
  }

  public Entity CreateAndLinkEntityLocal(INetickEntity userEntity)
  {
    return this.LinkEntity(userEntity, this.CreateEntity(Entity.GetStateSizeWords(userEntity.NetworkScripts)));
  }

  public Entity CreateEntity(int stateSizeWords)
  {
    return new Entity(this.Engine, (INetickEntity) null, (ISafeAllocator) this.MetaAllocator, (IAllocator) this.StateAllocator, (long) stateSizeWords, false, true, false);
  }

  public Entity LinkEntity(INetickEntity userEntity, Entity entity)
  {
    entity.CreateUserEntityData(userEntity);
    INetickScript[] allScripts = userEntity.AllScripts;
    for (int index = 0; index < allScripts.Length; ++index)
    {
      allScripts[index].Index = -1;
      allScripts[index].Initialize(this.Engine);
    }
    userEntity.Initialize(this.Engine, entity);
    entity.CalculateBehsHash();
    return entity;
  }

  internal void ServerAddEntity(INetickEntity obj, NetworkPlayer inputSource, bool invokeAwake)
  {
    if (this.AllEntities.Count >= this.Engine.Config.MaxObjects)
    {
      NetickLogger.LogError(this.Engine, (object) $"You are exceeding the maximum number of network objects at one time [{this.Engine.Config.MaxObjects}]. Consider increasing it in Netick Settings (Netick -> Settings).");
    }
    else
    {
      this._pendingAddedEntities.Add(obj.Entity);
      this.AddEntity(obj, this.Engine._entityMetaReplicator.Pop(), this._idCounter, inputSource, false, new NetickEntityMeta(), invokeAwake);
    }
  }

  internal void AddEntity(
    INetickEntity userEntity,
    int worldIndex,
    int networkId,
    NetworkPlayer inputSource,
    bool wasUnliked,
    NetickEntityMeta meta,
    bool invokeAwake)
  {
    if (this.Engine.IsServer)
      ++this._idCounter;
    if (wasUnliked)
      this.UnlinkedEntities.Remove(networkId);
    Entity entity = userEntity.Entity;
    entity.Init(worldIndex, networkId);
    entity.ApplyDefaultState(networkId);
    this.EntitiesWorldIndexArray[worldIndex] = entity;
    this.AllEntities.Add(networkId, entity);
    if (this.Engine.IsServer)
    {
      this.Engine._entityMetaReplicator.AddEntity(entity);
      if (entity.UserEntity.BroadPhaseFilter == BroadPhaseFilter.Global || !this.Engine.Config.EnableInterestManagement)
        this.Engine._IM.ServerSetEntityInterestGroup(entity, this.Engine._IM.GlobalGroup);
      this.Engine._gameEngine.PopulateEntityMeta(entity);
    }
    userEntity.NetworkRegister();
    if (this.Engine.IsServer)
    {
      entity.SetInputSource(inputSource, false);
      entity.UserEntity.NetworkAwake();
      this.Engine._gameEngine.OnEntitySpawned(entity);
      if (invokeAwake)
        entity.InvokeNetworkAwake();
    }
    this.InitEntity(entity, this.StateAllocator._allocator, wasUnliked);
    if (this.Engine.IsClient && (!this.Engine.Config.EnableInterestManagement || this.Engine.InterestManagement.IsWithInClientAOI(entity, meta.GroupIndex)))
      this.Engine._cliSim.ClientPendingAddedEntities.Add(entity);
    if (!this.Engine.IsClient)
      return;
    entity.SafeId = -1;
  }

  internal void DestroyEntity(Entity entity, bool instant, bool freeMemory)
  {
    if (entity.IsMarkedForDestruction)
      NetickLogger.LogWarning(this.Engine, (object) $"Tried to destroy an entity that is already marked for destruction. Entity ID: {entity.RawId}");
    entity.IsMarkedForDestruction = true;
    if (this.UnlinkedEntities.ContainsValue(entity))
    {
      this.RemoveEntity(entity, true, true);
    }
    else
    {
      Simulation.EntityRemove entityRemove = new Simulation.EntityRemove()
      {
        Entity = entity,
        FreeMem = freeMemory
      };
      if (instant)
      {
        this.RemoveEntity(entityRemove.Entity, entityRemove.FreeMem, false);
        this._pendingAddedEntities.Remove(entity);
        this._pendingRemovedEntities.Remove(entityRemove);
      }
      else
        this._pendingRemovedEntities.Add(entityRemove);
    }
  }

  private unsafe void RemoveEntity(Entity entity, bool freeMem, bool isUnliked)
  {
    if (!this.Engine.IsRunning)
      NetickLogger.LogError(this.Engine, (object) "Tried to an destroy object when Netick was not running.");
    if (this.Engine.IsClient & isUnliked)
    {
      this.Engine._gameEngine.OnUnlinkedEntityDespawned(entity);
      this.UnlinkedEntities.Remove(entity.RawId);
      if (entity.WorldIndex != -1)
        this.EntitiesWorldIndexArray[entity.WorldIndex] = (Entity) null;
    }
    else if (entity.RawId != -1)
    {
      InterestManagementGroup internalInterestGroup = entity.InternalInterestGroup;
      int groupTableId = entity.GroupTableId;
      entity.InternalInterestGroup = (InterestManagementGroup) null;
      entity.GroupTableId = -1;
      this.Engine._gameEngine.OnEntityDespawned(entity);
      this.SimulatedEntities.RemoveAndInvokeNetworkDestroy(entity);
      entity.UserEntity.NetworkUnregister();
      entity.InternalInterestGroup = internalInterestGroup;
      entity.GroupTableId = groupTableId;
      if (this.Engine.IsServer)
        this.Engine._entityMetaReplicator.RemoveEntity(entity);
      entity.InternalInterestGroup?.RemoveEntity(entity, entity.GroupTableId);
      entity.InternalInterestGroup = (InterestManagementGroup) null;
      this.EntitiesList.RemoveUnordered(entity);
      this.AllEntities.Remove(entity.RawId);
      this.ActiveEntities.Remove(entity.RawId);
      this.EntitiesWorldIndexArray[entity.WorldIndex] = (Entity) null;
      if (this.Engine.IsClient)
        this.Engine._cliSim.RemoveEntity(entity);
      if (entity.UserEntity.UseSAP)
        this.Engine._adaptiveReplicator?.RemoveEntity(entity);
      NetworkPlayer inputSource = entity.InputSource;
      if (inputSource != null)
        inputSource.ControlledEntities.RemoveUnordered(entity);
      entity.Reset();
    }
    entity.RawId = -1;
    entity.SafeId = -1;
    entity.WorldIndex = -1;
    entity.GroupTableId = -1;
    entity.InternalInputSourceIndex = -1;
    if (!freeMem || (IntPtr) entity.StatePtr == IntPtr.Zero)
      return;
    this.StateAllocator.Free(entity.StateOffsetBytes, entity.BlockIndex);
    this.MetaAllocator.Free(entity.MetaPtr);
    entity.StatePtr = (int*) null;
    entity.MetaPtr = (void*) null;
  }

  public bool FetchInput<T>(
    out T input,
    ref bool isDuplicated,
    Entity entity,
    int localPlayerIndex)
    where T : unmanaged
  {
    isDuplicated = false;
    input = default (T);
    int inputSourceIndex = entity.InternalInputSourceIndex;
    if (inputSourceIndex == -1)
      return false;
    return this.Engine.IsServer ? (inputSourceIndex == -2 ? this.Engine._srvSim.FetchInputLocal<T>(out input, ref isDuplicated, localPlayerIndex) : this.Engine._srvSim.FetchInputFor<T>(inputSourceIndex, out input, ref isDuplicated, localPlayerIndex)) : inputSourceIndex == this.Engine._cliSim.LocalInputSourceIndex && this.Engine._cliSim.FetchInputLocal<T>(out input, ref isDuplicated, localPlayerIndex);
  }

  public unsafe bool FetchInputLocal<T>(out T input, ref bool isDuplicated, int localPlayerIndex) where T : unmanaged
  {
    input = default (T);
    if (!this.Engine.IsInputEnabled || this.CurrentInput == null || this.CurrentInput.IsDuplicated && !this.Engine._config.InputReuseAtLowFPS || this.CurrentInput.Ignore)
      return false;
    isDuplicated = this.CurrentInput.IsDuplicated;
    int num = this.Engine._reflectionData.InputTypeToId[typeof (T)];
    for (int index = 0; index < this.CurrentInput.Inputs.Count; ++index)
    {
      if (this.CurrentInput.Inputs[index].Type == num && this.CurrentInput.Inputs[index].PlayerIndex == localPlayerIndex)
      {
        input = *(T*) this.CurrentInput.Inputs[index].Ptr;
        return true;
      }
    }
    return false;
  }

  public unsafe T GetInput<T>(int index) where T : unmanaged
  {
    int* ptr;
    return this.TryGetInputPtr<T>(index, out ptr) ? *(T*) ptr : default (T);
  }

  public unsafe void SetInput<T>(T input, int index) where T : unmanaged
  {
    int* ptr;
    if (!this.TryGetInputPtr<T>(index, out ptr))
      return;
    *(T*) ptr = input;
  }

  private unsafe bool TryGetInputPtr<T>(int index, out int* ptr) where T : unmanaged
  {
    ptr = (int*) null;
    if (!this.Engine.IsInputEnabled)
      return false;
    int key = this.Engine._reflectionData.InputTypeToId[typeof (T)] + index;
    InputBlock inputBlock;
    if (this._inputProviderTable.ContainsKey(key))
    {
      inputBlock = this._inputProviderTable[key];
    }
    else
    {
      int inputType = this.Engine._reflectionData.InputTypeToId[typeof (T)];
      inputBlock = this.AllocInputBlock(this.Engine._reflectionData.IdToInputSizeBytes[inputType / 10], index, inputType);
      this._inputProviderTable.Add(key, inputBlock);
    }
    if (!this._inputProvidersUsed.ContainsKey(key))
      this._inputProvidersUsed.Add(key, inputBlock);
    ptr = inputBlock.Ptr;
    return true;
  }

  internal SimulationInput GetInput()
  {
    return this._inputsPool.Count <= 0 ? new SimulationInput() : this._inputsPool.Dequeue();
  }

  internal unsafe SimulationInput CreateInput(Tick srvTick, Tick targetTick, float alpha)
  {
    SimulationInput input1 = this.GetInput();
    input1.SrvTick = srvTick;
    input1.TargetTick = targetTick;
    input1.Alpha = alpha;
    input1.CreationTime = this.Engine._timer.Time;
    input1.Ignore = !this.Engine.IsServer && !this.Engine._cliSim.SimulationReady;
    input1.IsDuplicated = !this.Engine._timer.IsFirstCall;
    foreach (InputBlock inputBlock in this._inputProvidersUsed.Values)
    {
      InputBlock input2 = this.AllocInputBlock(inputBlock.SizeBytes, inputBlock.PlayerIndex, inputBlock.Type);
      inputBlock.CopyTo(input2.Ptr);
      input1.AddInput(input2);
    }
    return input1;
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private unsafe InputBlock AllocInputBlock(int sizeBytes, int playerIndex, int inputType)
  {
    void* ptr = this._inputAllocator.Allocate((long) sizeBytes);
    MemoryAllocation.Clear(ptr, (long) sizeBytes);
    return new InputBlock(inputType, playerIndex, (int*) ptr, sizeBytes);
  }

  internal unsafe void RecycleInput(SimulationInput input)
  {
    for (int index = 0; index < input.Inputs.Count; ++index)
      this._inputAllocator.Free((void*) input.Inputs[index].Ptr, input.Inputs[index].SizeBytes);
    input.Reset();
    this._inputsPool.Enqueue(input);
  }

  internal void ResetInputProviders()
  {
    foreach (InputBlock inputBlock in this._inputProviderTable.Values)
      inputBlock.Reset();
  }

  internal void ChangeInputEnabled(bool inputEnabled)
  {
    this._inputEnabled = inputEnabled;
    this.ResetInputProviders();
  }

  internal static void WriteInputs(NetickEngine engine, BitBuffer buffer, int mtu)
  {
    Tick currentTick = engine._cliSim.CurrentTick;
    List<SimulationInput> savedInputs = engine._cliSim.SavedInputs;
    Dictionary<int, InputBlock> lastInputsCache = engine._cliSim._lastInputsCache;
    bool enableLagCompensation = engine.Config.EnableLagCompensation;
    lastInputsCache.Clear();
    buffer.AddInt(currentTick.Value);
    int writePos1 = buffer.WritePos;
    buffer.Add(6, 0U);
    if (savedInputs.Count < 1)
      return;
    byte num1 = 0;
    int count = savedInputs.Count;
    SimulationInput lastInput = (SimulationInput) null;
    int num2 = 5;
    int num3 = Math.Max(savedInputs.Count - num2, 0);
    for (int index = savedInputs.Count - 1; index >= num3 && (int) num1 < count; --index)
    {
      int writePos2 = buffer.WritePos;
      SimulationInput simInput = savedInputs[index];
      Simulation.WriteSimInput(buffer, simInput, lastInputsCache, lastInput, enableLagCompensation);
      lastInput = simInput;
      if (buffer.Length() > mtu - 20)
      {
        buffer.WritePos = writePos2;
        break;
      }
      ++num1;
    }
    buffer.Insert(writePos1, 3, (uint) num1);
  }

  internal void ReadInputs(
    NetickEngine engine,
    ReflectionData refData,
    ServerSimulation.ClientData client,
    BitBuffer buffer)
  {
    client.InputPackCache.Clear();
    this._lastInputsCache.Clear();
    Tick tick = buffer.ReadTick();
    uint num = buffer.Read(6);
    bool enableLagCompensation = engine.Config.EnableLagCompensation;
    SimulationInput lastInput = (SimulationInput) null;
    for (int index = 0; (long) index < (long) num; ++index)
    {
      SimulationInput input = this.GetInput();
      input.TargetTick = tick - index;
      this.ReadSimInput(refData, buffer, input, this._lastInputsCache, lastInput, enableLagCompensation);
      client.InputPackCache.Add(input);
      lastInput = input;
    }
    if (client.InputPackCache.Count < 1)
      return;
    for (int index = client.InputPackCache.Count - 1; index >= 0; --index)
    {
      if (!client.ReceiveInput(client.InputPackCache[index]))
        this.RecycleInput(client.InputPackCache[index]);
    }
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private static unsafe void WriteSimInput(
    BitBuffer buffer,
    SimulationInput simInput,
    Dictionary<int, InputBlock> lastInputs,
    SimulationInput lastInput,
    bool lagCompEnabled)
  {
    if (lagCompEnabled)
    {
      float alpha = simInput.Alpha;
      int num1 = lastInput != null ? simInput.SrvTick - lastInput.SrvTick : simInput.SrvTick.Value;
      int num2 = 0;
      NetickUtils.SetFloat(&num2, alpha, 10000f);
      buffer.AddInt(num2);
      buffer.AddInt(num1);
    }
    buffer.Add(6, (uint) simInput.Inputs.Count);
    buffer.AddBool(simInput.IsDuplicated);
    buffer.AddBool(simInput.Ignore);
    int num3 = 0;
    foreach (InputBlock input in simInput.Inputs)
    {
      int num4 = input.SizeBytes / 4;
      int key = input.Type + input.PlayerIndex;
      buffer.AddInt4(key - num3);
      int num5 = !lastInputs.ContainsKey(key) ? 1 : 0;
      num3 = key;
      if (num5 != 0)
      {
        for (int index = 0; index < num4; ++index)
        {
          buffer.AddBool(false);
          buffer.AddInt(input.Ptr[index]);
        }
        lastInputs.Add(key, input);
      }
      else
      {
        InputBlock lastInput1 = lastInputs[key];
        for (int index = 0; index < num4; ++index)
        {
          int num6 = input.Ptr[index] - lastInput1.Ptr[index];
          buffer.AddBool(num6 == 0);
          if (num6 != 0)
            buffer.AddInt(num6);
        }
        lastInputs[key] = input;
      }
    }
  }

  private unsafe void ReadSimInput(
    ReflectionData refData,
    BitBuffer buffer,
    SimulationInput simInput,
    Dictionary<int, InputBlock> lastInputs,
    SimulationInput lastInput,
    bool lagCompEnabled)
  {
    if (lagCompEnabled)
    {
      float num = NetickUtils.GetFloat(&buffer.ReadInt(), 0.0001f);
      simInput.Alpha = num;
      simInput.SrvTick = buffer.ReadTick() + (lastInput != null ? lastInput.SrvTick.Value : 0);
    }
    uint num1 = buffer.Read(6);
    simInput.IsDuplicated = buffer.ReadBool();
    simInput.Ignore = buffer.ReadBool();
    int num2 = 0;
    for (int index1 = 0; (long) index1 < (long) num1; ++index1)
    {
      int key = buffer.ReadInt4() + num2;
      int inputType = key / 10 * 10;
      int playerIndex = key % 10;
      num2 = key;
      bool flag = !lastInputs.ContainsKey(key);
      InputBlock input = this.AllocInputBlock(refData.IdToInputSizeBytes[inputType / 10], playerIndex, inputType);
      int num3 = input.SizeBytes / 4;
      if (!flag)
      {
        InputBlock lastInput1 = lastInputs[key];
        lastInput1.CopyTo(input.Ptr);
        for (int index2 = 0; index2 < num3; ++index2)
        {
          if (!buffer.ReadBool())
            input.Ptr[index2] = buffer.ReadInt() + lastInput1.Ptr[index2];
        }
      }
      else
      {
        for (int index3 = 0; index3 < num3; ++index3)
        {
          if (!buffer.ReadBool())
            input.Ptr[index3] = buffer.ReadInt();
        }
      }
      if (flag)
        lastInputs.Add(key, input);
      else
        lastInputs[key] = input;
      simInput.AddInput(input);
    }
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  internal void AttachBehaviour(INetickScript behaviour, bool addToResimulationLoop = true)
  {
    foreach (Simulation.BehaviourAttachOperation pendingBehaviour in this._pendingBehaviours)
    {
      if (pendingBehaviour.Behaviour == behaviour && pendingBehaviour.Attach)
        return;
    }
    this._pendingBehaviours.Enqueue(new Simulation.BehaviourAttachOperation()
    {
      Attach = true,
      Behaviour = behaviour,
      Resimulate = addToResimulationLoop
    });
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  internal void DetachBehaviour(INetickScript behaviour)
  {
    foreach (Simulation.BehaviourAttachOperation pendingBehaviour in this._pendingBehaviours)
    {
      if (pendingBehaviour.Behaviour == behaviour && !pendingBehaviour.Attach)
        return;
    }
    this._pendingBehaviours.Enqueue(new Simulation.BehaviourAttachOperation()
    {
      Attach = false,
      Behaviour = behaviour
    });
  }

  internal void DrainPendingBehaviours()
  {
    while (this._pendingBehaviours.Count > 0)
    {
      Simulation.BehaviourAttachOperation behaviourAttachOperation = this._pendingBehaviours.Dequeue();
      if (behaviourAttachOperation.Attach)
      {
        if (this.Engine.IsClient && behaviourAttachOperation.Resimulate)
          this.Engine._cliSim.PredictedEntities.AddBehaviour(behaviourAttachOperation.Behaviour);
        this.SimulatedEntities.AddBehaviour(behaviourAttachOperation.Behaviour);
        behaviourAttachOperation.Behaviour.NetworkStart();
      }
      else
      {
        if (this.Engine.IsClient)
          this.Engine._cliSim.PredictedEntities.RemoveBehaviour(behaviourAttachOperation.Behaviour);
        this.SimulatedEntities.RemoveBehaviour(behaviourAttachOperation.Behaviour);
        behaviourAttachOperation.Behaviour.NetworkDestroy();
      }
    }
  }

  protected void InvokeRPCs()
  {
    NetworkRpc.InvokeRpcs(this.Engine, (NetworkConnection) this.Engine.Client.ConnectedServer);
  }

  protected void InvokePlayerJoinLeaveCallbacks()
  {
    this.Engine._playersReplicator.DispatchCallbacks();
  }

  protected void InvokePreRollback()
  {
    Action preRollback = this.Engine.PreRollback;
    if (preRollback != null)
      preRollback();
    for (int index = 0; index < this.Engine.ClientSimulationCallbacks.Count; ++index)
      this.Engine.ClientSimulationCallbacks[index].OnPreRollback();
  }

  protected void InvokePostResimulation()
  {
    Action postResimulation = this.Engine.PostResimulation;
    if (postResimulation != null)
      postResimulation();
    for (int index = 0; index < this.Engine.ClientSimulationCallbacks.Count; ++index)
      this.Engine.ClientSimulationCallbacks[index].OnPostResimulation();
  }

  internal void InvokeOnChangedForRollback()
  {
    this.Engine._cliSim.IsResimulating = true;
    foreach (OnChangedData changeInfo in this._changesCache)
    {
      if (changeInfo.IsServer != 5)
      {
        try
        {
          changeInfo.Event((INetickScript) changeInfo.Behaviour, changeInfo);
        }
        catch (Exception ex)
        {
          INetickNetworkScript behaviour = changeInfo.Behaviour;
          NetickLogger.LogError((object) ex, (object) behaviour);
        }
      }
    }
    this.Engine._cliSim.IsResimulating = false;
  }

  internal void InvokeOnChangedForMisprediction()
  {
    foreach (OnChangedData onChangedData in this._changesCache)
    {
      try
      {
        this.TryInvokeRemoteOnChanged(onChangedData);
      }
      catch (Exception ex)
      {
        INetickNetworkScript behaviour = onChangedData.Behaviour;
        NetickLogger.LogError((object) ex, (object) behaviour);
      }
    }
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  private unsafe void TryInvokeRemoteOnChanged(OnChangedData onChangedData)
  {
    Entity entity = onChangedData.Behaviour.Entity;
    OnChangedMethod onChangedMethod = entity.ObjectMeta.PropertyElementsToPropertyStartIndex[onChangedData.PropertyIndex];
    if (onChangedMethod.Event == null)
      return;
    int* numPtr1 = (int*) ((IntPtr) this.Engine._cliSim._preRollback.Blocks[entity.BlockIndex].Ptr + (IntPtr) entity.StateOffsetBytes + (IntPtr) onChangedMethod.PropertyIndex * 4);
    int* numPtr2 = (int*) ((IntPtr) this.Engine._cliSim.StateAllocator._allocator.Blocks[entity.BlockIndex].Ptr + (IntPtr) entity.StateOffsetBytes + (IntPtr) onChangedMethod.PropertyIndex * 4);
    int sizeWords = onChangedData.SizeWords;
    int num = onChangedData.Offset * sizeWords;
    onChangedData.PreviousValue = numPtr1;
    onChangedData.IsServer = 1;
    for (int index1 = 0; index1 < sizeWords; ++index1)
    {
      int index2 = index1 + num;
      if (numPtr1[index2] != numPtr2[index2])
      {
        onChangedData.Event((INetickScript) onChangedData.Behaviour, onChangedData);
        break;
      }
    }
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  internal unsafe void OnEntityStateChangedLocal(
    Entity entity,
    int dataId,
    int isServer,
    int* previousPtr,
    bool delayedOnChanged)
  {
    OnChangedMethod changedMethod = entity.ObjectMeta.PropertyElementsToPropertyStartIndex[dataId];
    if (changedMethod.Event == null)
      return;
    if (this.Engine.IsResimulating && changedMethod.InvokeDuringResims == 0)
    {
      this.OnEntityStateChangedLocalPending(ref changedMethod, entity, dataId);
    }
    else
    {
      int isArray = changedMethod.IsArray;
      int* numPtr = previousPtr + changedMethod.PropertyIndex;
      int num1 = dataId - changedMethod.PropertyIndex;
      int num2 = isArray == 1 ? (num1 - num1 % changedMethod.PropertySizeWords) / changedMethod.PropertySizeWords : changedMethod.PropertyIndex;
      OnChangedData changeInfo = new OnChangedData()
      {
        InversePrecision = changedMethod.InversePrecision,
        Event = changedMethod.Event,
        SizeWords = changedMethod.PropertySizeWords,
        PreviousValue = numPtr,
        Offset = num2,
        IsServer = isServer,
        Behaviour = entity.UserEntity.NetworkScripts[changedMethod.BehaviourIndex]
      };
      if (!delayedOnChanged)
      {
        try
        {
          changeInfo.Event((INetickScript) changeInfo.Behaviour, changeInfo);
        }
        catch (Exception ex)
        {
          INetickNetworkScript behaviour = changeInfo.Behaviour;
          NetickLogger.LogError((object) ex, (object) behaviour);
        }
      }
      else
        this._changesCache.Add(changeInfo);
    }
  }

  [MethodImpl(MethodImplOptions.AggressiveInlining)]
  internal unsafe void OnEntityStateChangedRemote(
    OnChangedMethod[] onChangedTable,
    Entity entity,
    int dataId)
  {
    OnChangedMethod onChangedMethod = onChangedTable[dataId];
    if (onChangedMethod.Event == null)
      return;
    int isArray = onChangedMethod.IsArray;
    int* numPtr = (int*) ((IntPtr) this.Engine._cliSim.Previous.Blocks[entity.BlockIndex].Ptr + (IntPtr) entity.StateOffsetBytes + (IntPtr) onChangedMethod.PropertyIndex * 4);
    int num1 = dataId - onChangedMethod.PropertyIndex;
    int num2 = isArray == 1 ? (num1 - num1 % onChangedMethod.PropertySizeWords) / onChangedMethod.PropertySizeWords : onChangedMethod.PropertyIndex;
    this._changesCache.Add(new OnChangedData()
    {
      InversePrecision = onChangedMethod.InversePrecision,
      PropertyIndex = onChangedMethod.PropertyIndex,
      SizeWords = onChangedMethod.PropertySizeWords,
      Event = onChangedMethod.Event,
      IsServer = 5 + onChangedMethod.InvokeDuringResims,
      PreviousValue = numPtr,
      Behaviour = entity.UserEntity.NetworkScripts[onChangedMethod.BehaviourIndex],
      Offset = isArray == 1 ? num2 : 0
    });
  }

  internal unsafe void OnEntityStateChangedLocalPending(
    ref OnChangedMethod changedMethod,
    Entity entity,
    int dataId)
  {
    int isArray = changedMethod.IsArray;
    int* numPtr = (int*) ((IntPtr) this.Engine._cliSim.Previous.Blocks[entity.BlockIndex].Ptr + (IntPtr) entity.StateOffsetBytes + (IntPtr) changedMethod.PropertyIndex * 4);
    int num1 = dataId - changedMethod.PropertyIndex;
    int num2 = isArray == 1 ? (num1 - num1 % changedMethod.PropertySizeWords) / changedMethod.PropertySizeWords : changedMethod.PropertyIndex;
    this._changesCache.Add(new OnChangedData()
    {
      InversePrecision = changedMethod.InversePrecision,
      PropertyIndex = changedMethod.PropertyIndex,
      SizeWords = changedMethod.PropertySizeWords,
      Event = changedMethod.Event,
      IsServer = 5 + changedMethod.InvokeDuringResims,
      PreviousValue = numPtr,
      Behaviour = entity.UserEntity.NetworkScripts[changedMethod.BehaviourIndex],
      Offset = isArray == 1 ? num2 : 0
    });
  }

  protected internal struct BehaviourAttachOperation
  {
    public bool Attach;
    public bool Resimulate;
    public INetickScript Behaviour;
  }

  protected internal struct InputSourceChangedEvent
  {
    public Entity Entity;
    public NetworkPlayer PreviousPlayer;
  }

  protected internal struct EntityRemove : IEquatable<Simulation.EntityRemove>
  {
    public Entity Entity;
    public bool FreeMem;

    public override int GetHashCode() => this.Entity.GetHashCode();

    public override bool Equals(object obj)
    {
      return obj is Simulation.EntityRemove entityRemove && entityRemove.Entity == this.Entity;
    }

    public bool Equals(Simulation.EntityRemove other) => this.Entity == other.Entity;
  }
}
